package com.colabo.j2ee.web.core.exception;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicLong;

/**
 * 异常统计处理类的实现
 */
public class ExceptionStatistics {
	private static final long serialVersionUID = 1L;
	private final Log logger = LogFactory.getLog(ExceptionStatistics.class);

	// 异常类型数量
	private AtomicLong count = new AtomicLong(0);

	// 如果有异常嵌套的情况，是否在信息中需要记录
	private AtomicBoolean trace = new AtomicBoolean(false);

	// 异常统计后的数量
	private Map statsCount = Collections.synchronizedMap(new HashMap());

	// 异常详细信息
	private Map statsExc = Collections.synchronizedMap(new HashMap());

	// 用于存放异常统计信息的持久层设备
	private String persistenceStorage;

	public long getCount() {
		return count.get();
	}

	public long findCountByException(String exception) {
		Long result = (Long) statsCount.get(exception);
		return (result != null) ? result.longValue() : 0;
	}

	/**
	 * 产生xml格式文件
	 */
	public String toXML() {
		return this.toString();
	}

	public String toXML(String exception) {
		if (exception != null && statsCount.containsKey(exception)) {
			StringBuffer sb = new StringBuffer();
			Long number = (Long) this.statsCount.get(exception);
			Iterator infos = ((List) statsExc.get(exception)).iterator();
			this.createExceptionElement(sb, number, exception, infos);
			return sb.toString();
		}
		return "Not entry for the given exception";
	}

	public void setTraceEnabled(boolean traceEnabled) {
		trace.set(traceEnabled);
	}

	public boolean isTraceEnabled() {
		return trace.get();
	}

	public String getPersistenceStorage() {
		return persistenceStorage;
	}

	/**
	 * 设置储存位置
	 */
	public void setPersistenceStorage(String persistenceStorage) {
		this.persistenceStorage = persistenceStorage;
	}

	/**
	 * 产生xml格式的异常统计格式文件，如下
	 * <p>
	 * <ExceptionStatistics count="X"> <exception name="" count=""> <method
	 * name="" className="" time=""/> </exception> ... </ExceptionStatistics>
	 * </p>
	 * 
	 * @return XML格式的异常统计信息
	 */
	public String toString() {
		StringBuffer tmp = new StringBuffer("<ExceptionStatistics count='");
		tmp.append(count);
		tmp.append("' >\n");
		Map copys = new HashMap(statsCount);
		Map copy = new HashMap(statsExc);
		Iterator iter = copys.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry) iter.next();
			Long number = (Long) entry.getValue();
			String exception = (String) entry.getKey();
			Iterator infos = ((List) copy.get(exception)).iterator();
			createExceptionElement(tmp, number, exception, infos);
		}
		tmp.append("</ExceptionStatistics>");
		return tmp.toString();
	}

	/**
	 * 记录异常信息，一般用于捕获强制异常，如自定义的ServiceException等 使用场景:由于是捕获自定义的异常，因此主要用于手工捕获异常的地方
	 * 
	 * @parma throwable 异常源
	 * @parma methodName 抛出异常所在方法名
	 * @parma className 抛出异常所在类名
	 * @parma time 捕获异常时间
	 */
	public void updateStatistics(MessageException throwable, String methodName, String className, Date time) {
		this.count.getAndIncrement();
		String thr = throwable.getClass().getName();
		long number = (statsCount.containsKey(thr)) ? ((Long) statsCount.get(thr)).longValue() : 0;
		this.statsCount.put(thr, new Long(++number));

		ExceptionInfo info = new ExceptionInfo();
		info.methodName = methodName;
		info.className = className;
		info.time = time;
		info.throwable = throwable;
		List infos = (List) this.statsExc.get(thr);
		if (infos == null) {
			infos = new ArrayList();
		}
		infos.add(info);
		this.statsExc.put(thr, infos);
	}

	/**
	 * 记录异常信息，一般用于捕获非强制异常环境下的uncaught exception
	 * 使用场景:由于是捕获系统异常，因此主要用于拦截方式的例如Aspect或者Advice方式的日志拦截
	 * 
	 * @parma throwable 异常源
	 * @parma methodName 抛出异常所在方法名
	 * @parma className 抛出异常所在类名
	 * @parma time 捕获异常时间
	 */
	public void updateStatistics(Throwable throwable, String methodName, String className, Date time) {
		this.count.getAndIncrement();
		String thr = throwable.getClass().getName();
		long number = (statsCount.containsKey(thr)) ? ((Long) statsCount.get(thr)).longValue() : 0;
		this.statsCount.put(thr, new Long(++number));

		ExceptionInfo info = new ExceptionInfo();
		info.methodName = methodName;
		info.className = className;
		info.time = time;
		info.throwable = throwable;
		List infos = (List) this.statsExc.get(thr);
		if (infos == null) {
			infos = new ArrayList();
		}
		infos.add(info);
		this.statsExc.put(thr, infos);
	}

	/**
	 * 清空
	 */
	public void reset() {
		this.count.set(0L);
		this.statsCount.clear();
		this.statsExc.clear();
	}

	/**
	 * 把异常信息存储到指定的持久设备， 持久设备的地址是通过setPersistenceStorage（）方法完成的
	 */
	public void persist() {
		if (this.persistenceStorage != null) {
			// persist information to a persistence storage [disk]
			OutputStream out = null;
			try {
				// 如果不存在该指定文件，先创建
				File f = new File(this.persistenceStorage);
				if (!f.exists()) {
					f.createNewFile();
				}
				// 指定FileWriter的append属性为true，可以追加信息内容
				FileWriter fw = new FileWriter(this.persistenceStorage, true);
				String xml = toXML();
				fw.write(xml);
				fw.close();

				/**
				 * 另一种做法是通过FileOutputStream来完成，但需要对是否在原来文件基础上追加内容进行修改 out = new
				 * FileOutputStream(f); if (xml!=null && xml.length()>0) {
				 * //write content out.write(xml.getBytes()); }
				 */

			} catch (FileNotFoundException e) {
				if (logger.isInfoEnabled()) {
					logger.info("File Not Found exception arised during", e);
				}
			} catch (IOException e) {
				if (logger.isInfoEnabled()) {
					logger.info("Problems during creation of File " + this.persistenceStorage, e);
				}
			} catch (SecurityException e) {
				if (logger.isInfoEnabled()) {
					logger.info("Security exception during creation of File " + this.persistenceStorage, e);
				}
			} finally {
				try {
					if (out != null) {
						out.close();
					}
				} catch (IOException ioe) {
					if(logger.isDebugEnabled()){
						logger.debug(ioe);
					}
				}
			}
		}
	}

	private void createExceptionElement(StringBuffer tmp, Long number, String exception, Iterator infos) {
		tmp.append("<exception name='").append(exception).append("' ");
		tmp.append(" count='").append(number).append("'>\n");
		while (infos.hasNext()) {
			ExceptionInfo info = (ExceptionInfo) infos.next();
			tmp.append("<method name='");
			tmp.append(info.methodName);
			tmp.append("' className='");
			tmp.append(info.className);
			tmp.append("' time='");
			tmp.append(info.time);
			tmp.append("' >\n");

			if (this.isTraceEnabled()) {
				if (info.throwable instanceof MessageException) {
					// 如果是自己定义的扩展exception类型，则需要用自定义的方法进行解析
					MessageException me = (MessageException) info.throwable;
					tmp.append("<trace>").append("\n" + me.getTip() + "\n" + me.stackTraceString).append("</trace>\n");
				} else if (info.throwable instanceof Throwable) {
					// 如果只采用java自带的throwable的子类而不是自定义的exception类型，可用下面的
					tmp.append("<trace>").append("\n" + ExceptionUtils.getFullStackTrace(info.throwable)).append(
							"</trace>\n");
				}
			}
			tmp.append("</method>\n");
		}
		tmp.append("</exception>\n");
	}

	/**
	 * 异常信息
	 *
	 */
	private class ExceptionInfo implements Serializable {
		private static final long serialVersionUID = 1L;
		public String methodName;
		public String className;
		public Date time;
		public Throwable throwable;

		public String toString() {
			StringBuffer sb = new StringBuffer("Exception Info [");
			sb.append("method name :: ").append(methodName);
			sb.append("service name :: ").append(className);
			sb.append("time :: ").append(time);
			sb.append("]\n");
			return sb.toString();
		}
	}
}
